package readWriteFieldsRelations;


import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import java.lang.reflect.Constructor;
import util.CanBeUsedAs;
import util.Log;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.*;
import model.Filter;
import model.StatementKind;
import model.MethodDeclared;
import model.ConstructorDeclared;
public class MethodRelations implements Opcodes {
	  public static final String lineSep = System.getProperty("line.separator");

	//all classes
	  private final Collection<Class<?>> classesToTest;
	  
	  //collections to keep method dependence
	  protected  Map<Class<?>, Map<Method, ReadWriteFields>> fieldReadWrites =
	    new LinkedHashMap<Class<?>, Map<Method, ReadWriteFields>>();  
	  protected  Map<Class<?>, ReadWriteFields> constructorReadWrites =
		    new LinkedHashMap <Class<?>, ReadWriteFields>();  

	  protected final Map<Class<?>, Map<StatementKind, List<StatementKind>>> dependences =
	    new LinkedHashMap<Class<?>, Map<StatementKind, List<StatementKind>>>();  
	  protected final Map<Class<?>, List<StatementKind>> constructorDependences =
	    new LinkedHashMap<Class<?>, List<StatementKind>>();
	  protected final Map<Class<?>, Map<String, List<StatementKind>>> statementWriteFiled =
		    new LinkedHashMap<Class<?>, Map<String, List<StatementKind>>>();
	  protected final Map<Class<?>, Map<String, List<StatementKind>>> privateStatementDependences =
		    new LinkedHashMap<Class<?>, Map<String, List<StatementKind>>>();
 
	  /**
	   * Package visible constructor
	   * */
	  MethodRelations(Collection<Class<?>> classesToTest) {
		  this.classesToTest=classesToTest;
	  }
	  public Map<Class<?>, Map<String, List<StatementKind>>> getStatementWriteFiled(){
		  return statementWriteFiled;
	  }
	  public Map<Class<?>, Map<StatementKind, List<StatementKind>>> getStatementDependences(){
		  return dependences;
	  }
	  public Map<Class<?>, Map<String, List<StatementKind>>> getPrivateDependences(){
		  return privateStatementDependences;
	  }

	  /**
	   * The main entry of building relations between methods
	   * */
	  void buildRelations(List<StatementKind> models) throws IOException {
	    //analyze each class
	    for (Class<?> cls : classesToTest) {
			if (!Filter.canUse(cls))
				continue;
			if (Filter.isPrimitiveOrStringType(cls))
				continue;
			if (cls.equals(Object.class))
				continue;

			ClassReader cr = new ClassReader(cls.getName());
			ClassNode cn = new ClassNode();
			cr.accept(cn, ClassReader.SKIP_FRAMES);
			 Map<Class<?>, ReadWriteFields> constructorsReadWrites =analyzeConstructorClass(cls, cn);
			this.constructorReadWrites.put(cls,constructorsReadWrites.get(cls));

			Map<Method, ReadWriteFields> methodReadWrites = analyzeClass(cls,cn);
			//	      
			this.fieldReadWrites.put(cls, methodReadWrites);
		}
	    this.mergeCallEffects();
	    this.mergeConstructorEffects();
	    System.out.println("Compute method dependence ...");
	    Log.log("Compute method dependence ...");
	    this.computeDependenceConstructor(models);
	    this.computeDependence(models);
	    Log.log("Compute statement write fileds ...");
	    this.buildWriteFiledMethodDependence(models);
	  }
	 private void buildWriteFiledMethodDependence(List<StatementKind> models){
		    //keep a map to speed up lookup
		    Map<String, StatementKind> methodStmtMap = this.buildMethodStatementMapping(models);
	  //classify constructors
			Map<Class<?>, List<StatementKind>> constructors = new LinkedHashMap<Class<?>, List<StatementKind>>();
			for(StatementKind statement : models) {
			  Class<?> owner = this.getDeclaringClass(statement);
			  if(!constructors.containsKey(owner)) {
			    constructors.put(owner, new LinkedList<StatementKind>());
			  }
			  if(statement instanceof ConstructorDeclared) {
			    constructors.get(owner).add(statement);
			  }
			}

		  for(Entry<Class<?>, Map<Method, ReadWriteFields>> entry : this.fieldReadWrites.entrySet()) {
			  Class<?> clazz=entry.getKey();
		      Map<String, List<StatementKind>> statementMap =
			        new LinkedHashMap<String, List<StatementKind>>();

			  Map<Method, ReadWriteFields> methodSummary = entry.getValue();
			  for(Entry<Method, ReadWriteFields> mfields: methodSummary.entrySet()) {
				  ReadWriteFields fields = mfields.getValue();
			       for(Entry<String, Integer> writes : fields.writeFields.entrySet()) {
			    	   String fieldName=writes.getKey().toString();
//			    	   System.err.println("fieldName: " + fieldName);
				        StatementKind statementWrietField =  methodStmtMap.get(mfields.getKey().toGenericString());// this.getStatement(models, methodEntry.getKey());
				        if(statementWrietField == null) {
				          //System.err.println("No key? " + methodEntry.getKey());
				          continue;
				        }
			    	    if(statementMap.get(fieldName) == null){
			    	    	statementMap.put(fieldName, new LinkedList<StatementKind>());
			    	    }
			    		List<StatementKind> methodList = (List<StatementKind>) statementMap.get(fieldName);
			    		if(!methodList.contains(statementWrietField))
			    				methodList.add(statementWrietField);
 
			       }

			  }
			  if(statementMap.size()==0) continue;
		      this.statementWriteFiled.put(clazz, statementMap);
		    }
		  for(Entry<Class<?>, ReadWriteFields> mfields: this.constructorReadWrites.entrySet()){
			  Class<?> cls=mfields.getKey();
			  Map<String, List<StatementKind>> statementMap =this.statementWriteFiled.get(cls);
			  if(statementMap==null)
				  statementMap= new LinkedHashMap <String, List<StatementKind>>();   
			  ReadWriteFields fields = mfields.getValue();
			  for(Entry<String, Integer> writes : fields.writeFields.entrySet()) {
				  String fieldName=writes.getKey().toString();
		    	    if(statementMap.get(fieldName) == null)
		    	    	statementMap.put(fieldName, new LinkedList<StatementKind>());

				 // List<StatementKind> statementWrietField =  constructors.get(cls);
		    	    List<StatementKind> statementWrietField= this.constructorDependences.get(cls);
				  if(statementWrietField==null) continue;
				  List<StatementKind> methodList = (List<StatementKind>) statementMap.get(fieldName);
				  if(!methodList.contains(statementWrietField)){
	    				methodList.addAll(statementWrietField);
				  }

		  }
			  this.statementWriteFiled.put(cls, statementMap);
		  }

		    //reclaim the memory
		    methodStmtMap.clear();


	  }
	  /**
	   * Returns a list of related method
	   * */
	  List<StatementKind> getRelatedMethods(Method method) {
	    Class<?> owner = method.getDeclaringClass();
	    if(!this.dependences.containsKey(owner)) {
	      return new LinkedList<StatementKind>();
	    }
	    if(!this.dependences.get(owner).containsKey(method)) {
	      return new LinkedList<StatementKind>();
	    }
	    return this.dependences.get(owner).get(method);
	  }
	  
	  
	  /**
	   * All private methods below
	   * */
	  @SuppressWarnings("unchecked")
	  private Map<Method, ReadWriteFields> analyzeClass(Class<?> cls, ClassNode cn) {
	    Map<Method, ReadWriteFields> methodReadWrites =
	      new LinkedHashMap<Method,ReadWriteFields>();
	    //analyze each method
	    List<MethodNode> methodNodes = cn.methods;    
	    for(MethodNode methodNode : methodNodes) {
	      Method method = this.getMethod(cls, methodNode);
	      if(method == null) {
	        continue;
	      }
//	      System.out.println("analyze Method:"+ method.toGenericString());
//	      System.out.println("return type:"+ method.getReturnType());
		    Class<?>[] args=method.getParameterTypes();
	//	    	System.out.println("Argument Types:"+ Arrays.asList(args));

	      ReadWriteFields readWrites = this.getReadWriteFields(cls, methodNode);
	      // Add the return and argumnets typs class
	      readWrites.argusTyp(new HashSet<Class<?>>(Arrays.asList(args)));
	      readWrites.returnTyp(method.getReturnType());
	      
	      methodReadWrites.put(method, readWrites);
	    }
	    
	    //merge call sequence here
	//    return this.mergeCallEffects(methodReadWrites);
	    return methodReadWrites;
	  }
	  @SuppressWarnings("unchecked")
	  private Map<Class<?>, ReadWriteFields> analyzeConstructorClass(Class<?> cls, ClassNode cn) {
		Map<Class<?>, ReadWriteFields> methodReadWrites = new LinkedHashMap<Class<?>, ReadWriteFields>();
		// analyze each method
		List<MethodNode> methodNodes = cn.methods;
		for (MethodNode methodNode : methodNodes) {
			if (!methodNode.name.equals("<init>"))
				continue;
	//	    System.out.println("analyze Constructor Class:"+ cls.getName());
			ReadWriteFields readWrites = this.getReadWriteFields(cls,methodNode);
		
			methodReadWrites.put(cls, readWrites);
		}
		Constructor[] allConstructors = cls.getDeclaredConstructors();
		Set<Class<?>> argsTpe=new HashSet<Class<?>>();
		for (Constructor ctor : allConstructors) {
			Class<?>[] pType  = ctor.getParameterTypes();
			argsTpe.addAll(Arrays.asList(pType));
		}
		ReadWriteFields readWrites =methodReadWrites.get(cls);
		readWrites.argusTyp(argsTpe);
		// merge call sequence here
		// return this.mergeCallEffects(methodReadWrites);
		return methodReadWrites;
	}

	  private ReadWriteFields getReadWriteFields(Class<?> cls, MethodNode methodNode) {
			List<String> uitlFields=new LinkedList<String>();
		    ReadWriteFields readWrites = new ReadWriteFields();
		    InsnList mlist = methodNode.instructions;
	    	 
		    for(int i = 0; i < mlist.size(); i++) {
		      AbstractInsnNode insn = mlist.get(i);
		      int opCode = insn.getOpcode();
		      
		      switch(opCode) {
		      case ARRAYLENGTH:
		    	  if(uitlFields.size()!=0){
		 //       	  System.out.println("read array length:"+ uitlFields.get(0));
		    		  uitlFields.remove(uitlFields.size()-1);
		    	  }
		    	  break;
		      case LASTORE:
		      case FASTORE:
		      case DASTORE:
		      case IASTORE :
		      case CASTORE:
		      case AASTORE:
		      case BASTORE:
		      	  if(uitlFields.size()!=0){
			    	  readWrites.write(uitlFields.get(uitlFields.size()-1));
					  uitlFields.remove(uitlFields.size()-1);
		      	  }
		    	  
		    	  break;
		      case GETFIELD:
		          FieldInsnNode fieldGet = (FieldInsnNode)insn;
		           uitlFields.add(fieldGet.name);
		          readWrites.read(fieldGet.name);
		          break;
		        case PUTFIELD:
		          FieldInsnNode fieldPut = (FieldInsnNode)insn;
		          if(uitlFields.size()!=0&&uitlFields.contains(fieldPut.name))
		        	  	uitlFields.remove(uitlFields.size()-1);
		          readWrites.write(fieldPut.name);
		          break;
		        case GETSTATIC:
		          FieldInsnNode fieldStaticGet = (FieldInsnNode)insn;
		         if(fieldStaticGet.owner.startsWith("java")) continue;
		          if(!fieldStaticGet.owner.contains(cls.getName()))
		        	  uitlFields.add(fieldStaticGet.name);

		          readWrites.read(fieldStaticGet.name);
		          break;
		        case PUTSTATIC:
		          FieldInsnNode fieldStaticPut = (FieldInsnNode)insn;
		          if(!fieldStaticPut.desc.contains(cls.getName()))
		        	  uitlFields.add(fieldStaticPut.name);
		          readWrites.write(fieldStaticPut.name);
		          break;
		        case INVOKEVIRTUAL:
		        	
		          MethodInsnNode virtualMethod = (MethodInsnNode)insn;
		          if(virtualMethod.owner.startsWith("instrumentation")) continue;
		          if(virtualMethod.owner.startsWith("java/io/PrintStream")) continue;
		          if(!virtualMethod.owner.contains(cls.getName())||virtualMethod.owner.contains("[")){
		        	  if(RequestMethodType.contains(virtualMethod.name)){
		        		  if(uitlFields.size()!=0){
			        		  readWrites.write(uitlFields.get(uitlFields.size()-1));
			        		  uitlFields.remove(uitlFields.size()-1);
			        		 }       	  
		        	  break;
		        	  }
		         }
		          if(virtualMethod.owner.startsWith("java")) continue;
		         
		          readWrites.call(this.getMethod(virtualMethod.owner.replace('/', '.'), virtualMethod.name, virtualMethod.desc));
		          break;
		        case INVOKESTATIC:
		          MethodInsnNode staticMethod = (MethodInsnNode)insn;
		          if(staticMethod.owner.startsWith("instrumentation")) continue;
			
		          readWrites.call(this.getMethod(staticMethod.owner.replace('/', '.'), staticMethod.name, staticMethod.desc));
		          break;         
		        case INVOKESPECIAL:
		            MethodInsnNode privateMethod = (MethodInsnNode)insn;
		            if(privateMethod.name.equals("<init>"))
		            	readWrites.callClass(this.getClass(privateMethod.owner.replace('/', '.'), privateMethod.name, privateMethod.desc));
		            else
		            	readWrites.call(this.getMethod(privateMethod.owner.replace('/', '.'), privateMethod.name, privateMethod.desc));
		            break;   
			      case INVOKEINTERFACE:
			    	  MethodInsnNode  javaUtilMethod = (MethodInsnNode)insn;
			  // 	  System.out.println(javaUtilMethod.getType() + "  "+ javaUtilMethod.getClass().getName() +" " +javaUtilMethod.desc + " " + javaUtilMethod.owner + " "+ javaUtilMethod.name);
			    	  if(RequestMethodType.contains(javaUtilMethod.name))
			    		  if(uitlFields.size()!=0)
			    			  readWrites.write(uitlFields.get(uitlFields.size()-1));

			          if(uitlFields.size()!=0)
						  uitlFields.remove(uitlFields.size()-1);
			  
			       //   readWrites.call(this.getMethod(cls.getName(), javaUtilMethod.name, javaUtilMethod.desc));
			          readWrites.call(this.getMethodClassInterface(javaUtilMethod.owner.replace('/', '.'), javaUtilMethod.name, javaUtilMethod.desc));

			    	  break;

		        }
		    }
		    
		    return readWrites;
		  }
	  private Set<Method> getMethodClassInterface(String clsOwner, String methodName,
		String methodDesc) {
		  if(clsOwner.startsWith("java.")) return null;
		  Set<Method> methodCalls=new HashSet<Method>();
			try {
				Class<?> cls1 = Class.forName(clsOwner);
				for (Class<?> cls2 : classesToTest) {
					if (!CanBeUsedAs.canBeUsedAs(cls2, cls1))
						continue;
					Method[] methods = cls2.getDeclaredMethods();
					// Method[] methods = cls.getMethods();
					for (Method method : methods) {
						if (method.getName().equals(methodName)
								&& Type.getMethodDescriptor(method).equals(
										methodDesc)) {
							methodCalls.add(method);
						}
					}
				}
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
				System.exit(0);
			}
			if(methodCalls.size()>0)
				return methodCalls;
			return null;
	}

	  private Method getMethod(Class<?> cls, MethodNode methodNode) {
		  return this.getMethod(cls.getName(), methodNode.name, methodNode.desc);
	  }
	  
	  private Method getMethod(String clsOwner, String methodName, String methodDesc)   {
		    for(Class<?> clazz : classesToTest) {
		    	if(!clsOwner.equals(clazz.getName())) continue;
				  Method[] methods = clazz.getDeclaredMethods();
				//	  Method[] methods = cls.getMethods();
				    for(Method method : methods) {
				      if(method.getName().equals(methodName)&& Type.getMethodDescriptor(method).equals(methodDesc)) {
				        return method;
				      }
				    }
			    Class<?> javaClasses= clazz.getSuperclass();
			     methods = javaClasses.getDeclaredMethods();
			     for(Method method : methods) {
			         if(method.getName().equals(methodName)&& Type.getMethodDescriptor(method).equals(methodDesc)) {
			           return method;
			         }
			       }
				    }
	    return null;

	  }
	  private Class<?> getClass(String clsOwner, String methodName, String methodDesc)   {
		    for(Class<?> clazz : classesToTest) {
		    	if(!clsOwner.equals(clazz.getName())) continue;
		    		return clazz;
		    }
		    return null;
	  }
	  private Map<Class<?>, ReadWriteFields> mergeConstructorEffects() {
			 for(Entry<Class<?>, ReadWriteFields> Classes : this.constructorReadWrites.entrySet()) {
				 Class<?> thisCls=Classes.getKey();
				  ReadWriteFields mergedReadWrites = Classes.getValue().cloneWithoutCalls();
			      Set<Class<?>> alreadyVisited = new HashSet<Class<?>>();
			      List<Class<?>> worklist = new LinkedList<Class<?>>();
			      worklist.addAll(Classes.getValue().callClass());
			      alreadyVisited.addAll(Classes.getValue().callClass());
			      while(!worklist.isEmpty()) {
			        Class<?> first = worklist.remove(0);
			         ReadWriteFields fields= this.constructorReadWrites.get(first);
			        
			        if(fields == null) {
			          continue;
			        }
			        for(Entry<String, Integer> reads : fields.readFields.entrySet()) {
			          mergedReadWrites.read(reads.getKey(), reads.getValue());
			        }
			        for(Entry<String, Integer> writes : fields.writeFields.entrySet()) {
			          mergedReadWrites.write(writes.getKey(), writes.getValue());
			        }
			        for(Class<?> c : fields.callClass) {
			          if(!alreadyVisited.contains(c)) {
			            alreadyVisited.add(c);
			            worklist.add(c);	       
			            if(!mergedReadWrites.callClass.contains(c))
			            	mergedReadWrites.callClass.add(c);

			          }
			        }
			      }
			      //reclaim memory
			      alreadyVisited.clear();
			      worklist.clear();
			      this.constructorReadWrites.put(thisCls, mergedReadWrites);
			 }
			 
			 return null;
	  }
	    
	  private Map<Method, ReadWriteFields> mergeCallEffects() {
	    //create a new map
			 for(Entry<Class<?>, Map<Method, ReadWriteFields>> Classes : this.fieldReadWrites.entrySet()) {
				Map<Method, ReadWriteFields> methodReadWrites = Classes.getValue();
			    Map<Method, ReadWriteFields> retMap = new LinkedHashMap<Method, ReadWriteFields>();
			    
			    //merge effects transitively
			    for(Entry<Method, ReadWriteFields> entry : methodReadWrites.entrySet()) {
				      Method method = entry.getKey();
				      ReadWriteFields mergedReadWrites = entry.getValue().cloneWithoutCalls();
				      //using a standard worklist algorithm
				      Set<Method> alreadyVisited = new HashSet<Method>();
				      List<Method> worklist = new LinkedList<Method>();
				      worklist.addAll(entry.getValue().calls());
				      alreadyVisited.addAll(entry.getValue().calls());
				      while(!worklist.isEmpty()) {
					        Method first = worklist.remove(0);
						      //put the merged read/write to the return map
					        Class<?> firstCall=first.getDeclaringClass();
					        Map<Method, ReadWriteFields> methodCallReadWrites= this.fieldReadWrites.get(firstCall);
					        if(methodCallReadWrites==null) continue;
					        ReadWriteFields firstCallFields = methodCallReadWrites.get(first);
					        
					        if(firstCallFields == null) continue;
					        
					        for(Entry<String, Integer> reads : firstCallFields.readFields.entrySet()) {
					          mergedReadWrites.read(reads.getKey(), reads.getValue());
					        }
					        for(Entry<String, Integer> writes : firstCallFields.writeFields.entrySet()) {
					          mergedReadWrites.write(writes.getKey(), writes.getValue());
					        }
					        for(Method m : firstCallFields.calls) {
					          if(!alreadyVisited.contains(m)) {
					            alreadyVisited.add(m);
					            worklist.add(m);	       
					            if(!mergedReadWrites.calls.contains(m))
					            	mergedReadWrites.calls.add(m);
					          }
					        }
				      }
			      retMap.put(method, mergedReadWrites);
			      //reclaim memory
			      alreadyVisited.clear();
			      worklist.clear();
			    }
			    this.fieldReadWrites.put(Classes.getKey(), retMap);
			}
	    return null;
	  }
	  private void computeDependenceConstructor(List<StatementKind> models) {	
			 Map<String, StatementKind> methodStmtMap = this.buildMethodStatementMapping(models);

				Map<Class<?>, List<StatementKind>> constructors = new LinkedHashMap<Class<?>, List<StatementKind>>();
				for(StatementKind statement : models) {
				  Class<?> owner = this.getDeclaringClass(statement);
				  if(!constructors.containsKey(owner)) {
				    constructors.put(owner, new LinkedList<StatementKind>());
				  }
				  if(statement instanceof ConstructorDeclared) {
				    constructors.get(owner).add(statement);
				  }
				}
				 for(Entry<Class<?>, ReadWriteFields> entry : this.constructorReadWrites.entrySet()) {
					 Class<?> cls=entry.getKey();
					  List<StatementKind> ctors = constructors.get(cls);
					  if(ctors==null) continue;
				      List<StatementKind> dependentStatements = new LinkedList<StatementKind>(ctors);

 					 ReadWriteFields CallsReadWrites = entry.getValue();
					    Set<Class<?> > callThisClass = CallsReadWrites.callClass();
					    for(Class<?> callCls:callThisClass){
					    	if(constructors.containsKey(callCls)){
				        		  ctors = constructors.get(callCls);
					      	        if(ctors != null && !ctors.isEmpty()&& !dependentStatements.containsAll(ctors))
					      	        	dependentStatements.addAll(ctors);
					      	        
					    	}
					    }
				        //this argumnets type
				        Set<Class<?>> argsThisConstructor = CallsReadWrites.argusTyp();
					    for(Class<?> constructor:argsThisConstructor){
					        if(Filter.isPrimitiveOrStringType(constructor)) continue;
					         if(Filter.isAbstract(constructor) || !Filter.isVisible(constructor.getModifiers())) {
					        	 for(Class<?> clz2:constructors.keySet())
					        		 if(CanBeUsedAs.canBeUsedAs(clz2, constructor)){
					        			  ctors = constructors.get(clz2);
							      	        if(ctors != null && !ctors.isEmpty()&& !dependentStatements.containsAll(ctors)) {
							      	        	dependentStatements.addAll(ctors);
							      	        }
					        		 }
					         }else{
					        	 if(constructors.containsKey(constructor)){
					        		  ctors = constructors.get(constructor);
						      	        if(ctors != null && !ctors.isEmpty()&& !dependentStatements.containsAll(ctors)) {
						      	        	dependentStatements.addAll(ctors);
						      	        }
			//      			  System.err.println();
					        	 }
					         	}					   
					         }
					    //	methods return the same type of the structors arguments
					    for(Entry<Class<?>, Map<Method, ReadWriteFields>> rwMethods : this.fieldReadWrites.entrySet()) {
					    	Class<?> otherClass=rwMethods.getKey();
					    Map<Method, ReadWriteFields> otherMethodReadAndWrite= this.fieldReadWrites.get(otherClass);
					      Set<Method> otherMethods = otherMethodReadAndWrite.keySet();
					      for(Method otherMethod : otherMethods) {
						        Set<Class<?>> retrnOtherMethod = otherMethodReadAndWrite.get(otherMethod).returnTyp();
						        Class<?> retrnTpe= retrnOtherMethod.iterator().next();
						        if(!Filter.isPrimitiveOrStringType(retrnTpe)&&!retrnTpe.equals(void.class)&&!retrnTpe.toString().contains("java"))
							        if(argsThisConstructor.contains(retrnTpe)||argsThisConstructor.contains(Object.class)){
					        				StatementKind key =  methodStmtMap.get(otherMethod.toGenericString());
					        				if(key!=null)
					        					dependentStatements.add(key);
							        }
				
							    }

					      }
					    // call methods
					    Set<Method> thisClassCallMethods = CallsReadWrites.calls();
					    for(Method thisMethod : thisClassCallMethods) {
	//				    	System.out.println("thisMethod: "+thisMethod);
					    	 if(!Filter.canUse(thisMethod)) continue;
					    	 Map<Method, ReadWriteFields> thisMethodReadAndWrite= this.fieldReadWrites.get(thisMethod.getDeclaringClass());
					    	 if(thisMethodReadAndWrite==null) continue;
					    	 Set<String> readThisMethod = thisMethodReadAndWrite.get(thisMethod).readFields.keySet();
					    	  for(Entry<Class<?>, Map<Method, ReadWriteFields>> entry_ : this.fieldReadWrites.entrySet()) {
						    	  Class<?> className=entry_.getKey();
	//					    	  System.out.println("other className: "+className);
						    	  Map<Method, ReadWriteFields> methodAndReadWritesClass = entry_.getValue();
							      Set<Method> otherMethods = methodAndReadWritesClass.keySet();
								      for(Method otherMethod : otherMethods) {
							
									        if(otherMethod == thisMethod) {
									          continue;
									        }
									        if(otherMethod.getName().equals("toString")) continue;
//									    	System.out.println("otherMethod: "+otherMethod);
	
									        Set<String> writeOtherMethod = methodAndReadWritesClass.get(otherMethod).writeFields.keySet();
									        for(String writeField : writeOtherMethod) { 
										          if(readThisMethod.contains(writeField)) 
										            if(!dependentStatements.contains(otherMethod)){
										            	StatementKind key =  methodStmtMap.get(otherMethod.toGenericString());
										            	if(key==null)continue;
										            	dependentStatements.add(key);
										            }									          
										     }
								      }
						      }
					    	}

					     // StatementKind key =  constructors.get(cls);
					      if(dependentStatements.size()==0) continue;
					      this.constructorDependences.put(cls, dependentStatements);
				 }
	  }
	  /**
	   * Compute the dependence between each methods
	   * */
	  private void computeDependence(List<StatementKind> models) {	
			Map<Class<?>, List<StatementKind>> constructors = new LinkedHashMap<Class<?>, List<StatementKind>>();
			for(StatementKind statement : models) {
			  Class<?> owner = this.getDeclaringClass(statement);
			  if(!constructors.containsKey(owner)) {
			    constructors.put(owner, new LinkedList<StatementKind>());
			  }
			  if(statement instanceof ConstructorDeclared) {
			    constructors.get(owner).add(statement);
			  }
			}

		    for(Entry<Class<?>, Map<Method, ReadWriteFields>> entry : this.fieldReadWrites.entrySet()) {
				    Class<?> clazz = entry.getKey();
				    Map<Method, ReadWriteFields> methodAndReadWrites = entry.getValue();		      
		      		//the method dependence map
				    Map<StatementKind, List<StatementKind>>  methodMap = new LinkedHashMap<StatementKind, List<StatementKind>>();
		        	methodMap = this.computeRelatedMethod(methodAndReadWrites,constructors,models);		      
		        	//put the class and method dependence to the map
		        	this.dependences.put(clazz, methodMap);
		    }
		  }
	  
	 private Map<StatementKind, List<StatementKind>>  computeRelatedMethod(Map<Method, ReadWriteFields> methodAndReadWrites,Map<Class<?>, List<StatementKind>> constructors,List<StatementKind> models) {
		 Map<StatementKind, List<StatementKind>> relatedStatementsMap = new LinkedHashMap<StatementKind, List<StatementKind>>();

		 Map<String, StatementKind> methodStmtMap = this.buildMethodStatementMapping(models);

	    //then compute the dependence between each method
		    for(Method thisMethod : methodAndReadWrites.keySet()) {
		      //other methods
//		    	System.out.println("this: "+thisMethod);
	    	 
		    	 if(!Filter.canUse(thisMethod)) continue;
		      //the list add to the related method map
			        Set<String> readThisMethod = methodAndReadWrites.get(thisMethod).readFields.keySet();
			        Set<String> writeThisMethod = methodAndReadWrites.get(thisMethod).writeFields.keySet();
			        Set<Method> callThisMethod = methodAndReadWrites.get(thisMethod).calls();
	
		      List<Method> dependentMethods = new LinkedList<Method>();
		      List<StatementKind> dependentStatements = new LinkedList<StatementKind>();
	
			      for(Entry<Class<?>, Map<Method, ReadWriteFields>> entry : this.fieldReadWrites.entrySet()) {
			    	  Class<?> className=entry.getKey();
			    	  Map<Method, ReadWriteFields> methodAndReadWritesClass = entry.getValue();
				      Set<Method> otherMethods = methodAndReadWritesClass.keySet();
			      for(Method otherMethod : otherMethods) {
		
			        if(otherMethod == thisMethod) {
			          continue;
			        }
	//		        System.out.println("\t other:"+otherMethod);	
			        if(otherMethod.getName().equals("toString")) continue;
		
		
			      //  Set<String> readOtherMethod = methodAndReadWrites.get(otherMethod).readFields.keySet();
			        Set<String> writeOtherMethod = methodAndReadWritesClass.get(otherMethod).writeFields.keySet();
			        Set<Method> callOtherMethod = methodAndReadWritesClass.get(otherMethod).calls();
			        //calculate private call methods
					      Map<String, List<StatementKind>> statementMap =
						        new LinkedHashMap<String, List<StatementKind>>();

				        for(Method m:callThisMethod){
				        	if(!Filter.canUse(otherMethod)){
				        		if(!otherMethod.equals(m)) continue;
						        String pMethodName=privateMethdoName(m);
						        if(pMethodName==null) continue;
						        if(this.privateStatementDependences.get(className)==null)
						        	this.privateStatementDependences.put(className, statementMap);
						        statementMap=this.privateStatementDependences.get(className);
						        if(statementMap==null||statementMap.size()==0)
						        		statementMap.put(pMethodName, new LinkedList<StatementKind>());
						        List<StatementKind> list=statementMap.get(pMethodName);
						        if(list==null)
						        	list=new LinkedList<StatementKind>();
						        if(!list.contains(methodStmtMap.get(thisMethod.toGenericString())))
						        	list.add(methodStmtMap.get(thisMethod.toGenericString()));
						        statementMap.put(pMethodName, list);
						        this.privateStatementDependences.put(className, statementMap);
				        	}
				        }
				    	 if(!Filter.canUse(otherMethod)) continue;
		
			        //it should compute reversely
				        Set<String> readWriteFields = new HashSet<String>();
				        Set<String> readReadFields = new HashSet<String>();
				        //for(String readField : readOtherMethod) {
				        for(String writeField : writeOtherMethod) { 
				          if(writeThisMethod.contains(writeField)) {
				            readWriteFields.add(writeField);
				          }
				          if(readThisMethod.contains(writeField)) {
				            readReadFields.add(writeField);
				            if(!dependentMethods.contains(otherMethod)){
			            		dependentMethods.add(otherMethod);
				            	StatementKind key =  methodStmtMap.get(otherMethod.toGenericString());
				            	dependentStatements.add(key);
				            }
				          }
				        }
				       // See if public other method calls this method.
				        if(Filter.canUse(otherMethod)){
				        	for( Method m: callOtherMethod){
				        		if(m.equals(thisMethod)){
				        			if(!dependentMethods.contains(otherMethod)){
				        				StatementKind key =  methodStmtMap.get(otherMethod.toGenericString());
					            		dependentStatements.add(key);
				        					dependentMethods.add(otherMethod);
				        			}
				        		}
				        	}
				        }
				        //this argumnets type
				        Set<Class<?>> argsThisMethod = methodAndReadWrites.get(thisMethod).argusTyp();
				        //other return type
				        Set<Class<?>> retrnOtherMethod = methodAndReadWritesClass.get(otherMethod).returnTyp();
				        Class<?> retrnTpe= retrnOtherMethod.iterator().next();
			//	        System.out.println("\t other:"+retrnTpe);	  
				        if(!Filter.isPrimitiveOrStringType(retrnTpe)&&!retrnTpe.equals(void.class)&&!retrnTpe.toString().contains("java"))
					        if(argsThisMethod.contains(retrnTpe)||argsThisMethod.contains(Object.class))
					            if(!dependentMethods.contains(otherMethod)){
				            		dependentMethods.add(otherMethod);
			        				StatementKind key =  methodStmtMap.get(otherMethod.toGenericString());
			        				if(key!=null)
			        					dependentStatements.add(key);
		
					            }
					     
		
		
			      }
			        // return type for constructors
			        List<StatementKind> ctors = constructors.get(className);
			     	List<Class<?>> argsTpe=Arrays.asList(thisMethod.getParameterTypes());
		           for(Class<?> clz:argsTpe){
		  			 if(clz.equals(Object.class)
		  						||clz.equals(java.util.HashMap.class)
		  						|| clz.equals(Comparable.class) ) continue;
		      	     if(Filter.isPrimitiveOrStringType(clz) )continue;
				         if(Filter.isAbstract(clz) || !Filter.isVisible(clz.getModifiers())) {
				        	 for(Class<?> clz2:constructors.keySet())
				        		 if(CanBeUsedAs.canBeUsedAs(clz2, clz)){
				        			  ctors = constructors.get(clz2);
						      	        if(ctors != null && !ctors.isEmpty()&& !dependentStatements.containsAll(ctors)) {
						      	        	dependentStatements.addAll(ctors);
						      	        }
				        		 }
				         }else{
				        	 if(constructors.containsKey(clz)){
				        		  ctors = constructors.get(clz);
					      	        if(ctors != null && !ctors.isEmpty()&& !dependentStatements.containsAll(ctors)) {
					      	        	dependentStatements.addAll(ctors);
					      	        }
		//      			  System.err.println();
				        	 }
				         	}
		
				         }
		
			        //Calculate constructions
			        ReadWriteFields fields= this.constructorReadWrites.get(className);
			        if(fields!=null)
					  for(Entry<String, Integer> writes : fields.writeFields.entrySet()) {
						  String writeField=writes.getKey().toString();
				    	    if(readThisMethod.contains(writeField)){
								  List<StatementKind> statementWrietField =  constructors.get(className);
							  if(statementWrietField!=null)
								  if(!dependentStatements.containsAll(statementWrietField))
									  dependentStatements.addAll(statementWrietField);
				    	    }
					  }
		
			      }
		      
		      StatementKind key =  methodStmtMap.get(thisMethod.toGenericString());
		      if(dependentStatements.size()==0) continue;
		      relatedStatementsMap.put(key, dependentStatements);
		    }
	    return relatedStatementsMap;
	  }
	  
	  /**
	   * A helper method to get the declaring class of the given statement
	   * */
	  private Class<?> getDeclaringClass(StatementKind statement) {
	    if(statement instanceof MethodDeclared) {
	    		MethodDeclared rmethod = (MethodDeclared)statement;
	      return rmethod.getMethod().getDeclaringClass();
	    } else if(statement instanceof ConstructorDeclared) {
	    	ConstructorDeclared CreateObject = (ConstructorDeclared)statement;
	      return CreateObject.getConstructor().getDeclaringClass();
	    } else {
	      throw new RuntimeException("Unexpected statement type here: " + statement);
	    }
	  }
	  
	  /**
	   * Convert the method dependence to be statement dependence
	   * */
	  public String privateMethdoName(Method m) {
		if (!Modifier.isPrivate(m.getModifiers()))
			return null;
		String name = m.toString();
		 if(m.getReturnType().getName().toString().contains("["))
			 name = name.substring(name.indexOf(m.getReturnType().getCanonicalName()));
		 	else
		 		name = name.substring(name.indexOf(m.getReturnType().getName().toString()));
		name = name.replace(m.getDeclaringClass().getName().toString() + ".","".toString());
//		System.err.println("MethodRelations.privateMethdoName: "+name);
		int throwIndx = name.indexOf(") throws ");
		if (throwIndx > 0)
			name = name.replace(name.substring(throwIndx + 1, name.length()),"".toString());
		// System.err.println("MethodRelations.privateMethdoName: "+name);
		StringBuilder sb = new StringBuilder();
		sb.append(name);
		return sb.toString();
	}

	  private Map<String, StatementKind> buildMethodStatementMapping(List<StatementKind> models) {
	    Map<String, StatementKind> methodStmtMap = new LinkedHashMap<String, StatementKind>();
	    for(StatementKind statement : models) {
	      if(statement instanceof MethodDeclared) {
	        String signature = ((MethodDeclared)statement).getMethod().toGenericString();
	        methodStmtMap.put(signature, statement);
	      }
	    }
	    
	    return methodStmtMap;
	  }
	  /**
	   * Debugging help, to show the content
	   * */
	  String showDependence(Set<Class<?>> classToTest) {
	    StringBuilder sb = new StringBuilder();
	    for(Class<?> cls : classToTest) {
	      List<StatementKind> callClass =this.constructorDependences.get(cls);
		  sb.append("\n----------------------------");
		  sb.append("\nClass: ");
	      sb.append(cls);
	      sb.append("\nconstructors");
	      if(callClass!=null){
	        for(StatementKind s : callClass) {
		          sb.append("\n\t"+ s.convertString());
		        }
	      }

		      Map<StatementKind, List<StatementKind>> methodMap = this.dependences.get(cls);

	      for(Entry<StatementKind, List<StatementKind>> methods: methodMap.entrySet()) {
	    	  if(methods.getKey()==null)continue;
	        sb.append("\n\nmethod: ");
	        sb.append(methods.getKey().convertString());
	        List<StatementKind> methodsWrite = methods.getValue();
	        sb.append("\t" + methodsWrite.size());
	        for(StatementKind s : methodsWrite) {
	        	if(s instanceof MethodDeclared)
	        		sb.append("\n\t"+((MethodDeclared)s).getMethod().getDeclaringClass().getName()+" "  +  s.convertString());
	        	else
	        		sb.append("\n\t"+ s.convertString());
	        }
	        sb.append(lineSep );
	      }
	      sb.append(lineSep+lineSep );
	    }
	    
	    
	     return sb.toString();
	  }
	  String showStatmentsWriteFileds(Set<Class<?>> classToTest) {
		    StringBuilder sb = new StringBuilder();
		     sb.append("\n\t-------****------ show Statments Write Fileds -------****--------");
		    for(Entry<Class<?>, Map<String, List<StatementKind>>> entry : this.statementWriteFiled.entrySet()) {
				   if(!classToTest.contains(entry.getKey())) continue;

			     sb.append("\n----------------------------");
				 sb.append("\nClass: ");

			      sb.append(entry.getKey());
			      Map<String, List<StatementKind>> methodMap = entry.getValue();
			      for(Entry<String, List<StatementKind>> mfields: methodMap.entrySet()) {
				        sb.append("\nFiled: ");
				        sb.append(mfields.getKey());
//				        sb.append(lineSep);
				        List<StatementKind> methods = mfields.getValue();
				        sb.append("\t" + methods.size());
				        for(StatementKind s : methods) {
				          sb.append("\n\t" + s.convertString());
//				          sb.append(lineSep);
				        }
				      }
//				      sb.append(lineSep + lineSep);

				    
		    }
		    return sb.toString();
	  }	  
	  /**
	   * Debugging help, to show the content
	   * */
	  String showFieldReadWrites(Set<Class<?>> classToTest) {
	    StringBuilder sb = new StringBuilder();
	    
	   for(Entry<Class<?>, Map<Method, ReadWriteFields>> entry : this.fieldReadWrites.entrySet()) {
		   if(!classToTest.contains(entry.getKey())) continue;
		     sb.append("\n----------------------------");
		 sb.append("\nClass: ");

	     sb.append(entry.getKey());
	     sb.append(lineSep);
	     Map<Method, ReadWriteFields> methodSummary = entry.getValue();
	     for(Entry<Method, ReadWriteFields> mfields: methodSummary.entrySet()) {
	       sb.append("  method: ");
	       sb.append(mfields.getKey());
	       sb.append(lineSep);
	       ReadWriteFields fields = mfields.getValue();
	       sb.append("    All reads:" + lineSep);
	       for(Entry<String, Integer> reads : fields.readFields.entrySet()) {
	         sb.append("       " + reads.getKey()+ ", " + (reads.getValue()));
	         sb.append(lineSep);
	       }
	       sb.append("    All writes:" + lineSep);
	       for(Entry<String, Integer> writes : fields.writeFields.entrySet()) {
	         sb.append("       " + writes.getKey()+ ", " + (writes.getValue()));
	         sb.append(lineSep);
	       }
	       sb.append("    All Calls:" + lineSep);
	       for(Method mCall : fields.calls) {
	         sb.append("       " + mCall.toGenericString());
	         sb.append(lineSep);
	       }

	     }
	     sb.append(lineSep + lineSep);
	   }
	    
	    return sb.toString();
	  }

}